package org.zcl.utils;

import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.ReactiveRedisTemplate;
import org.springframework.data.redis.core.RedisTemplate;

import javax.annotation.Resource;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Function;

/**
 * @Package org.zcl.util
 * @PROJECT_NAME: question
 * @DESCRIPTION:
 * @USER: zhuchenglin
 * @DATE: 2021/12/24 14:07
 */
@Slf4j
public class CacheUtil {

    @Resource(name = "redisTemplate")
    private RedisTemplate redisTemplate;

    @Resource(name = "reactiveRedisTemplate")
    private ReactiveRedisTemplate reactiveRedisTemplate;

    @Autowired
    private RedissonClient redissonClient;

    public String getString(String key) {
        try {
            return (String) this.redisTemplate.opsForValue().get(key);
        } catch (Exception e) {
            log.error("获取缓存异常", e);
            return null;
        }
    }

    public Integer getInteger(String key) {
        try {
            return (Integer) this.redisTemplate.opsForValue().get(key);
        } catch (Exception e) {
            log.error("获取缓存异常", e);
            return null;
        }
    }

    public Long getLong(String key) {
        try {
            return (Long) this.redisTemplate.opsForValue().get(key);
        } catch (Exception e) {
            log.error("获取缓存异常", e);
            return null;
        }
    }

    public Double getDouble(String key) {
        try {
            return (Double) this.redisTemplate.opsForValue().get(key);
        } catch (Exception e) {
            log.error("获取缓存异常", e);
            return null;
        }
    }

    public <T> T get(String key) {
        return (T) this.redisTemplate.opsForValue().get(key);
    }

    public <T> T getSerial(String key) {
        return (T) this.reactiveRedisTemplate.opsForValue().get(key).block();
    }

    public <T> T get(String key, TypeReference<T> typeReference) {
        Object result = this.redisTemplate.opsForValue().get(key);

        if (result instanceof JSONObject) {
            JSONObject jsonObject = (JSONObject) result;
            return (T) jsonObject.toJavaObject(typeReference);
        }
        return (T) result;
    }

    public void set(String key, Object value) {
        if (value == null) {
            return;
        }
        set(key, value, 3600000L, TimeUnit.MILLISECONDS);
    }

    public void set(String key, Object value, long expireTime, TimeUnit timeUnit) {
        this.redisTemplate.opsForValue().set(key, value, expireTime, timeUnit);
    }

    public void setSerial(String key, Object value, long expireTime) {
        this.reactiveRedisTemplate.opsForValue().set(key, value, Duration.of(expireTime, ChronoUnit.SECONDS)).subscribe();
    }

    public boolean delete(String key) {
        return delete(key, false);
    }

    public boolean delete(String key, boolean match) {
        if (match) {
            Set keys = this.redisTemplate.keys(key + "*");
            long count = this.redisTemplate.delete(keys).longValue();
            return (count > 0L);
        }
        return this.redisTemplate.delete(key).booleanValue();
    }

    public Set<String> keys(String key) {
        return this.redisTemplate.keys(key + "*");
    }

    public long increment(String key, int num) {
        return redisTemplate.opsForValue().increment(key, num);
    }

    public boolean hasKey(String key) {
        return redisTemplate.hasKey(key);
    }

    public <T> T tryLock(String key, long waitTime, Function<Void, T> function) {
        return tryLock(key, waitTime, null, function);
    }

    public <T> T tryLock(String key, long waitTime, Long second, Function<Void, T> function) {
        RLock lock = null;
        try {
            if (StringUtils.isBlank(key) || Objects.isNull(function)) {
                return null;
            }
            lock = redissonClient.getLock("distributedLock:".concat(key));
            // 尝试加锁，最多等待30秒，上锁以后second秒自动解锁
            boolean flag = lock.tryLock(waitTime, Optional.ofNullable(second).orElse(-1L), TimeUnit.SECONDS);// NOSONAR
            if (!flag) {
                log.debug("获取锁失败：{}", key);
                throw new RuntimeException("系统繁忙，请稍后重试");// NOSONAR
            }
            return function.apply(null);
        } catch (Exception e) {// NOSONAR
            log.debug("获取锁异常：" + key, e);
            throw (e instanceof RuntimeException) ? (RuntimeException) e : new RuntimeException(e);
        } finally {
            //线程是否持有此锁定，如果还持有则释放，如果未持有则说明已被释放
            if (Objects.nonNull(lock) && lock.isHeldByCurrentThread()) {
                log.debug("释放锁：{}", key);
                lock.unlock();
            }
        }
    }

    public void tryLock(String key, long waitTime, Consumer<Void> consumer) {
        tryLock(key, waitTime, unused -> {
            consumer.accept(null);
            return null;
        });
    }

    public Integer decreaseStock(String key, Long quantity) {
        log.info("扣减库存key:{}", key);
        Integer stock = (Integer) reactiveRedisTemplate.opsForValue().get(key).block();
        if (stock == null) {
            return null;
        }
        if (stock >= quantity) {
            reactiveRedisTemplate.opsForValue().decrement(key, quantity).block();
        }
        stock = (Integer) reactiveRedisTemplate.opsForValue().get(key).block();
        return stock;
    }

    public Integer recovery(String key, long quantity) {
        log.info("恢复库存，key:{}", key);
        Integer stock = (Integer) reactiveRedisTemplate.opsForValue().get(key).block();
        if (stock == null) {
            return null;
        }
        reactiveRedisTemplate.opsForValue().increment(key, quantity).block();
        stock = (Integer) reactiveRedisTemplate.opsForValue().get(key).block();
        return stock;

    }
}
